/***********************************************************************
* Microsoft (R) Windows (R) Resource Compiler
*
* Copyright (c) Microsoft Corporation.	All rights reserved.
*
* File Comments:
*
*
***********************************************************************/

#include "rc.h"
#include <ddeml.h>

/************************************************************************/
/* Internal constants                                                   */
/************************************************************************/
#define GOT_IF                  1       /* last nesting command was an if.. */
#define GOT_ELIF                2       /* last nesting command was an if.. */
#define GOT_ELSE                3       /* last nesting command was an else */
#define GOT_ENDIF               4       /* found endif */
#define ELSE_OR_ENDIF           5       /* skip to either #else or #endif */
#define ENDIF_ONLY              6       /* skip to #endif -- #else is an error*/

int     ifstack[IFSTACK_SIZE];


/************************************************************************/
/* Local Function Prototypes                                            */
/************************************************************************/
void chk_newline(const wchar_t *);
void in_standard(void);
int incr_ifstack(void);
token_t next_control(void);
unsigned long int pragma(void);
int skipto(int);
void skip_quoted(int);
PWCHAR sysinclude(void);


/************************************************************************/
/* incr_ifstack - Increment the IF nesting stack                        */
/************************************************************************/

int
incr_ifstack(
    void
    )
{
    if(++Prep_ifstack >= IFSTACK_SIZE) {
        fatal(1052);
    }
    return(Prep_ifstack);
}


/************************************************************************
 * SYSINCLUDE - process a system include : #include <foo>
 *
 * ARGUMENTS - none
 *
 * RETURNS - none
 *
 * SIDE EFFECTS - none
 *
 * DESCRIPTION
 *      Get the system include file name.  Since the name is not a "string",
 *      the name must be built much the same as the -E option rebuilds the text
 *      by using the Tokstring expansion for tokens with no expansion already
 *
 *  NOTE : IS THIS ANSI? note we're just reading chars, and not expanding
 * any macros. NO, it's not. it must expand the macros.
 * TODO : have it call yylex() unless and until it finds a '>' or a newline.
 * (probably have to set On_pound_line to have yylex return the newline.)
 *
 * AUTHOR
 *                      Ralph Ryan      Sep. 1982
 *
 * MODIFICATIONS - none
 *
 *
 ************************************************************************/
PWCHAR
sysinclude(
    void
    )
{
    REG int     c;
    REG WCHAR  *p_fname;

    p_fname = Reuse_W;
    c = skip_cwhite();
    if( c == L'\n' ) {
        UNGETCH();
        error(2012);    /* missing name after '<' */
        return(NULL);
    }
    while( c != L'>' && c != L'\n' ) {
        *p_fname++ = (WCHAR)c;          /* check for buffer overflow ??? */
        c = get_non_eof();
    }
    if( c == L'\n' ) {
        UNGETCH();
        error(2013);    /* missing '>' */
        return(NULL);
    }
    if(p_fname != Reuse_W) {
        p_fname--;
        while((p_fname >= Reuse_W) && iswspace(*p_fname)) {
            p_fname--;
        }
        p_fname++;
    }
    *p_fname = L'\0';
    return(Reuse_W);
}


/************************************************************************
**  preprocess : the scanner found a # which was the first non-white char
**  on a line.
************************************************************************/
void
preprocess(
    void
    )
{
    REG WCHAR   c;
    long        eval;
    int         condition;
    token_t     deftok;
    hln_t       identifier;
    unsigned long int   cp;

    if(Macro_depth != 0) {      /* # only when not in a macro */
        return;
    }
    switch(CHARMAP(c = skip_cwhite())) {
        case LX_ID:
            getid(c);
            HLN_NAME(identifier) = Reuse_W;
            HLN_LENGTH(identifier) = (UINT)Reuse_W_length;
            HLN_HASH(identifier) = Reuse_W_hash;
            break;

        case LX_NL:
            UNGETCH();
            return;

        default:
            error(2019, c);    /* unknown preprocessor command */
            skip_cnew();    /* finds a newline */
            return;
    }

    On_pound_line = TRUE;
start:
    switch(deftok = is_pkeyword(HLN_IDENTP_NAME(&identifier))) {
        int                     old_prep;

        case P0_DEFINE :
            define();
            break;
        case P0_LINE :
            old_prep = Prep;
            Prep = FALSE;
            yylex();
            if(Basic_token != L_CINTEGER) {         /* #line needs line number */
                error(2005, TS_STR(Basic_token));        /* unknown preprocessor command */
                Prep = old_prep;
                skip_cnew();
                On_pound_line = FALSE;
                return;
            }
            /*
            **  -1 because there's a newline at the end of this line
            **  which will be counted later when we find it.
            **  the #line says the next line is the number we've given
            */
            Linenumber = TR_LVALUE(yylval.yy_tree) - 1;
            yylex();
            Prep = old_prep;
            switch(Basic_token) {
                case L_STRING:
                    if( wcscmp(Filename, yylval.yy_string.str_ptr) != 0) {
                        wcsncpy(Filename,
                            yylval.yy_string.str_ptr,
                            sizeof(Filebuff) / sizeof(WCHAR)
                            );
                    }

                case L_NOTOKEN:
                    break;

                default:
                    error(2130, TS_STR(Basic_token));         /* #line needs a string */
                    skip_cnew();
                    On_pound_line = FALSE;
                    return;
            }
            emit_line();
            chk_newline(L"#line");
            break;
        case P0_INCLUDE :
            old_prep = Prep;
            Prep = FALSE;
            InInclude = TRUE;
            yylex();
            InInclude = FALSE;
            Prep = old_prep;
            switch(Basic_token) {
                case L_LT:
                    if((sysinclude()) == NULL) {
                        skip_cnew();
                        On_pound_line = FALSE;
                        return;
                    }
                    yylval.yy_string.str_ptr = Reuse_W;
                    break;

                case L_STRING:
                    break;

                default:
                    error(2006, TS_STR(Basic_token));        /* needs file name */
                    skip_cnew();
                    On_pound_line = FALSE;
                    return;
                    break;
            }
            wcscpy(Reuse_Include, yylval.yy_string.str_ptr);
            chk_newline(L"#include");
            if( wcschr(Path_chars, *yylval.yy_string.str_ptr) ||
                (wcschr(Path_chars, L':') && (yylval.yy_string.str_ptr[1] == L':'))) {
                /*
                **  we have a string which either has a 1st char which is a path
                **  delimiter or, if ':' is a path delimiter (DOS), which has
                **  "<drive letter>:" as the first two characters.  Such names
                **  specify a fully qualified pathnames. Do not append the search
                **  list, just look it up.
                */
                if( ! newinput(yylval.yy_string.str_ptr, MAY_OPEN)) {
                    fatal(1015, Reuse_W); /* can't find include file */
                }
            }
            else if( (Basic_token != L_STRING) || (! nested_include())) {
                in_standard();
            }
            break;

        case P0_IFDEF :
        case P0_IFNDEF :
            if(CHARMAP(c = skip_cwhite()) != LX_ID) {
                fatal(1016);
            }
            getid(c);
            eval = (get_defined()) ? TRUE : FALSE;
            chk_newline((deftok == P0_IFDEF) ? L"#ifdef" : L"#ifndef");
            if(deftok == P0_IFNDEF) {
                eval = ( ! eval );
            }
            if( eval || ((condition = skipto(ELSE_OR_ENDIF)) == GOT_ELSE) ) {
                /*
                **  expression is TRUE or when we skipped the false part
                **  we found a #else that will be expanded.
                */
                ifstack[incr_ifstack()] = GOT_IF;
            } else if(condition == GOT_ELIF) {
                /* hash is wrong, but it won't be used */
                HLN_NAME(identifier) = L"if";                /* sleazy HACK */
                goto start;
            }
            break;

        case P0_IF :
            old_prep = Prep;
            Prep = FALSE;
            InIf = TRUE;
            eval = do_constexpr();
            InIf = FALSE;
            Prep = old_prep;
            chk_newline(PPifel_str /* "#if/#elif" */);
            if((eval) || ((condition = skipto(ELSE_OR_ENDIF)) == GOT_ELSE) ) {
                /*
                **  expression is TRUE or when we skipped the false part
                **  we found a #else that will be expanded.
                */
                ifstack[incr_ifstack()] = GOT_IF;
                if(Eflag && !eval)
                    emit_line();
            } else {
                /*
                **  here the #if's expression was false, so we skipped until we found
                **  an #elif. we'll restart and fake that we're processing a #if
                */
                if(Eflag)
                    emit_line();
                if(condition == GOT_ELIF) {
                    /* hash is wrong, but it won't be needed */
                    HLN_NAME(identifier) = L"if";            /* sleazy HACK */
                    goto start;
                }
            }
            break;
        case P0_ELIF :
            /*
            **  here, we have found a #elif. first check to make sure that
            **  this is not an occurrance of a #elif with no preceding #if.
            **  (if Prep_ifstack < 0) then there is no preceding #if.
            */
            if(Prep_ifstack-- < 0) {
                fatal(1018);
            }
            /*
            **  now, the preceding #if/#elif was true, and we've
            **  just found the next #elif. we want to skip all #else's
            **  and #elif's from here until we find the enclosing #endif
            */
            while(skipto(ELSE_OR_ENDIF) != GOT_ENDIF) {
                ;
            }
            if(Eflag)
                emit_line();
            break;

        case P0_ELSE :      /*  the preceding #if/#elif was true  */
            if((Prep_ifstack < 0) || (ifstack[Prep_ifstack--] != GOT_IF)) {
                fatal(1019); /*  make sure there was one  */
            }
            chk_newline(PPelse_str /* "#else" */);
            skipto(ENDIF_ONLY);
            if(Eflag)
                emit_line();
            break;

        case P0_ENDIF :     /*  only way here is a lonely #endif  */
            if(Prep_ifstack-- < 0) {
                fatal(1020);
            }
            if(Eflag)
                emit_line();
            chk_newline(PPendif_str /* "#endif" */);
            break;

        case P0_PRAGMA :
            cp = pragma();
            if (cp != 0) {
                if (cp == CP_WINUNICODE) {
                    if (fWarnInvalidCodePage) {
                        warning(4213);
                    } else {
                        fatal(4213);
                    }
                    break;
                }
                if (!IsValidCodePage(cp)) {
                    if (fWarnInvalidCodePage) {
                        warning(4214);
                    } else {
                        fatal(4214);
                    }
                    break;
                }
                if (cp != uiCodePage) {
                    if (!io_restart(cp)) {
                        fatal(1121);
                    }
                    uiCodePage = cp;    // can't be set until now!
                }
            }
            break;

        case P0_UNDEF :
            if(CHARMAP(c = skip_cwhite()) != LX_ID) {
                warning(4006);      /* missing identifier on #undef */
            } else {
                getid(c);
                undefine();
            }
            chk_newline(L"#undef");
            break;

        case P0_ERROR:
            {
                PWCHAR      p;

                p = Reuse_W;
                while((c = get_non_eof()) != LX_EOS) {
                    if(c == L'\n') {
                        UNGETCH();
                        break;
                    }
                    *p++ = c;
                }
                *p = L'\0';
            }
            error(2188, Reuse_W);
            chk_newline(L"#error");
            break;

        case P0_IDENT:
            old_prep = Prep ;
            Prep = FALSE;
            yylex();
            Prep = old_prep;
            if(Basic_token != L_STRING) {
                warning(4079, TS_STR(Basic_token));
            }
            chk_newline(L"#error");
            break;

        case P0_NOTOKEN:
            fatal(1021, HLN_IDENTP_NAME(&identifier));
            break;
    }
    On_pound_line = FALSE;
}


/************************************************************************
 * SKIPTO - skip code until the end of an undefined block is reached.
 *
 * ARGUMENTS
 *      short key - skip to an ELSE or ENDIF or just an ENDIF
 *
 * RETURNS  - none
 *
 * SIDE EFFECTS
 *      - throws away input
 *
 * DESCRIPTION
 *      The preprocessor is skipping code between failed ifdef, etc. and
 *      the corresponding ELSE or ENDIF (when key == ELSE_OR_ENDIF).
 *      Or it is skipping code between a failed ELSE and the ENDIF (when
 *      key == ENDIF_ONLY).
 *
 * AUTHOR - Ralph Ryan, Sept. 16, 1982
 *
 * MODIFICATIONS - none
 *
 ************************************************************************/
int
skipto(
    int key
    )
{
    REG int             level;
    REG token_t tok;

    level = 0;
    tok = P0_NOTOKEN;
    for(;;) {
        /* make sure that IF [ELSE] ENDIF s are balanced */
        switch(next_control()) {
            case P0_IFDEF:
            case P0_IFNDEF:
            case P0_IF:
                level++;
                break;
            case P0_ELSE:
                tok = P0_ELSE;
                /*
                            **  FALLTHROUGH
                            */
            case P0_ELIF:
                /*
                **  we found a #else or a #elif. these have their only chance
                **  at being valid if they're at level 0.
                **  if we're at any other level,
                **  then this else/elif belongs to some other #if and we skip them.
                **  if we were looking for an endif, the we have an error.
                */
                if(level != 0) {
                    tok = P0_NOTOKEN;
                    break;
                }
                if(key == ENDIF_ONLY) {
                    fatal(1022);   /* expected #endif */
                } else if(tok == P0_ELSE) {
                    chk_newline(PPelse_str /* "#else" */);
                    return(GOT_ELSE);
                } else {
                    return(GOT_ELIF);
                }
                break;
            case P0_ENDIF:
                if(level == 0) {
                    chk_newline(PPendif_str /* "#endif" */);
                    return(GOT_ENDIF);
                } else {
                    level--;
                }
                break;
        }
    }
}


/*************************************************************************
**  in_standard : search for the given file name in the directory list.
**              Input : ptr to include file name.
**              Output : fatal error if not found.
*************************************************************************/
void
in_standard(
    void
    )
{
    int     i;
    int     stop;
    WCHAR   *p_dir;
    WCHAR   *p_file;
    WCHAR   *p_tmp;

    stop = Includes.li_top;

    for(i = MAXLIST-1; i >= stop; i--) {
        p_file = yylval.yy_string.str_ptr;
        if( ((p_dir = Includes.li_defns[i])!=0) &&(wcscmp(p_dir, L"./") != 0) ) {
            /*
            **  there is a directory to prepend and it's not './'
            */
            p_tmp = Exp_ptr;
            while((*p_tmp++ = *p_dir++) != 0)
                ;
            /*
            **  above loop increments p_tmp past null.
            **  this replaces that null with a '/' if needed.  Not needed if the
            **  last character of the directory spec is a path delimiter.
            **  we then point to the char after the '/'
            */
            if(wcschr(Path_chars, p_dir[-2]) == 0) {
                p_tmp[-1] = L'/';
            } else {
                --p_tmp;
            }
            while((*p_tmp++ = *p_file++) != 0)
                ;
            p_file = Exp_ptr;
        }
        if(newinput(p_file,MAY_OPEN)) { /* this is the non-error way out */
            return;
        }
    }

    fatal(1015, yylval.yy_string.str_ptr);       /* can't find include file */
}


/*************************************************************************
**  chk_newline : check for whitespace only before a newline.
**  eat the newline.
*************************************************************************/
void chk_newline(const wchar_t *cmd)
{
    if(skip_cwhite() != L'\n') {
        warning(4067, cmd);          /* cmd expected newline */
        skip_cnew();
    } else {
        UNGETCH();
    }
}

/*************************************************************************
**  skip_quoted : skips chars until it finds a char which matches its arg.
*************************************************************************/
void
skip_quoted(
    int sc
    )
{
    REG WCHAR   c;

    for(;;) {
        switch(CHARMAP(c = GETCH())) {
            case LX_NL:
                warning(4093);
                UNGETCH();
                return;
                break;
            case LX_DQUOTE:
            case LX_SQUOTE:
                if(c == (WCHAR)sc)
                    return;
                break;
            case LX_EOS:
                if(handle_eos() == BACKSLASH_EOS) {
                    SKIPCH();       /* might be /" !! */
                }
                break;
            case LX_LEADBYTE:
                get_non_eof();
                break;
        }
    }
}


/*************************************************************************
**  next_control : find a newline. find a pound sign as the first non-white.
**  find an id start char, build an id look it up and return the token.
**  this knows about strings/char const and such.
*************************************************************************/
token_t
next_control(
    void
    )
{
    REG WCHAR   c;

    for(;;) {
        c = skip_cwhite();
first_switch:
        switch(CHARMAP(c)) {
            case LX_NL:
                Linenumber++;
                // must manually write '\r' with '\n' when writing 16-bit strings
                if(Prep) {
                    myfwrite(L"\r\n", 2 * sizeof(WCHAR), 1, OUTPUTFILE);
                }
                if((c = skip_cwhite()) == L'#') {
                    if(LX_IS_IDENT(c = skip_cwhite())) {
                        /*
                        **  this is the only way to return to the caller.
                        */
                        getid(c);
                        return(is_pkeyword(Reuse_W));       /* if its predefined  */
                    }
                }
                goto first_switch;
                break;
            case LX_DQUOTE:
            case LX_SQUOTE:
                skip_quoted(c);
                break;
            case LX_EOS:
                if(handle_eos() == BACKSLASH_EOS) {
                    SKIPCH();       /* might be \" !! */
                }
                break;
        }
    }
}


/*************************************************************************
**  do_defined : does the work for the defined(id)
**              should parens be counted, or just be used as delimiters (ie the
**              first open paren matches the first close paren)? If this is ever
**              an issue, it really means that there is not a legal identifier
**              between the parens, causing an error anyway, but consider:
**              #if (defined(2*(x-1))) || 1
**              #endif
**              It is friendlier to allow compilation to continue
*************************************************************************/
int
do_defined(
    PWCHAR p_tmp
    )
{
    REG UINT    c;
    REG int     value=0;
    int         paren_level = 0;

    /*
    ** we want to allow:
    **      #define FOO             defined
    **      #define BAR(a,b)        a FOO | b
    **      #define SNAFOO          0
    **      #if FOO BAR
    **      print("BAR is defined");
    **      #endif
    **      #if BAR(defined, SNAFOO)
    **      print("FOO is defined");
    **      #endif
    */
    if(wcscmp(p_tmp,L"defined") != 0) {
        return(0);
    }
    if((!can_get_non_white()) && (Tiny_lexer_nesting == 0)) {
        /* NL encountered */
        return(value);
    }
    if((c = CHECKCH())== L'(') { /* assumes no other CHARMAP form of OPAREN */
        *Exp_ptr++ = (WCHAR)c;
        SKIPCH();
        paren_level++;
        if((!can_get_non_white()) && (Tiny_lexer_nesting == 0)) {
            /* NL encountered */
            return(value);
        }
    }
    if(Tiny_lexer_nesting>0) {
        if((CHARMAP((WCHAR)(c=CHECKCH()))==LX_MACFORMAL) || (CHARMAP((WCHAR)c)==LX_ID)) {
            SKIPCH();
            tl_getid((UCHAR)c);
        }
    } else {
        if(LX_IS_IDENT(((WCHAR)(c = CHECKCH())))) {
            SKIPCH();
            if(Macro_depth >0) {
                lex_getid((WCHAR)c);
            } else {
                getid((WCHAR)c);
            }
            value = (get_defined()) ? TRUE : FALSE;
        } else {
            if(paren_level==0) {
                error(2003);
            } else {
                error(2004);
            }
        }
    }
    if((CHARMAP(((WCHAR)(c = CHECKCH()))) == LX_WHITE) || (CHARMAP((WCHAR)c) == LX_EOS)) {
        if( ! can_get_non_white()) {
            return(value);
        }
    }
    if(paren_level) {
        if((CHARMAP(((WCHAR)(c = CHECKCH()))) == LX_CPAREN)) {
            SKIPCH();
            paren_level--;
            *Exp_ptr++ = (WCHAR)c;
        }
    }

    if((paren_level > 0) && (Tiny_lexer_nesting == 0)) {
        warning(4004);
    }

    return(value);
}


/*************************************************************************
 * NEXTIS - The lexical interface for #if expression parsing.
 * If the next token does not match what is wanted, return FALSE.
 * otherwise Set Currtok to L_NOTOKEN to force scanning on the next call.
 * Return TRUE.
 * will leave a newline as next char if it finds one.
 *************************************************************************/
int
nextis(
    register token_t tok
    )
{
    if(Currtok != L_NOTOKEN) {
        if(tok == Currtok) {
            Currtok = L_NOTOKEN;                        /*  use up the token  */
            return(TRUE);
        } else {
            return(FALSE);
        }
    }
    switch(yylex()) {                           /*  acquire a new token  */
        case 0:
            break;
        case L_CONSTANT:
            if( ! IS_INTEGRAL(TR_BTYPE(yylval.yy_tree))) {
                    fatal(1017);
            } else {
                Currval = TR_LVALUE(yylval.yy_tree);
            }

            if(tok == L_CINTEGER) {
                return(TRUE);
            }

            Currtok = L_CINTEGER;
            break;

        case L_IDENT:
            Currval = do_defined(HLN_IDENTP_NAME(&yylval.yy_ident));
            if(tok == L_CINTEGER) {
                return(TRUE);
            }
            Currtok = L_CINTEGER;
            break;

        default:
            if(tok == Basic_token) {
                return(TRUE);
            }
            Currtok = Basic_token;
            break;
    }

    return(FALSE);
}


/************************************************************************
**  skip_cnew : reads up to and including the next newline.
************************************************************************/
void
skip_cnew(
    void
    )
{
    for(;;) {
        switch(CHARMAP(GETCH())) {
            case LX_NL:
                UNGETCH();
                return;
            case LX_SLASH:
                skip_comment();
                break;
            case LX_EOS:
                handle_eos();
                break;
        }
    }
}


/************************************************************************
**  skip_NLonly : reads up to the next newline, disallowing comments
************************************************************************/
void
skip_NLonly(
    void
    )
{
    for(;;) {
        switch(CHARMAP(GETCH())) {
            case LX_NL:
                UNGETCH();
                return;
            case LX_EOS:
                handle_eos();
                break;
        }
    }
}


/************************************************************************
**  pragma : handle processing the pragma directive
**  called by preprocess() after we have seen the #pragma
**  and are ready to handle the keyword which follows.
************************************************************************/
unsigned long
pragma(
    void
    )
{
    WCHAR   c;
    unsigned long int cp=0;

    c = skip_cwhite();
    if (c != L'\n') {
        getid(c);
        _wcsupr(Reuse_W);
        if (wcscmp(L"CODE_PAGE", Reuse_W) == 0) {
            if ((c = skip_cwhite()) == L'(') {
                c = skip_cwhite();  // peek token
                if (iswdigit(c)) {
                    token_t tok;
                    int old_prep = Prep;

                    Prep = FALSE;
                    tok = getnum(c);
                    Prep = old_prep;

                    switch(tok) {
                        default:
                        case L_CFLOAT:
                        case L_CDOUBLE:
                        case L_CLDOUBLE:
                        case L_FLOAT:
                        case L_DOUBLE:
                            break;
                        case L_CINTEGER:
                        case L_LONGINT:
                        case L_CUNSIGNED:
                        case L_LONGUNSIGNED:
                        case L_SHORT:
                        case L_LONG:
                        case L_SIGNED:
                        case L_UNSIGNED:
                            cp = TR_LVALUE(yylval.yy_tree);
                            break;
                    }
                }
                if (cp == 0) {
                    getid(c);
                    _wcsupr(Reuse_W);
                    if (wcscmp(L"DEFAULT", Reuse_W) == 0) {
                        cp = uiDefaultCodePage;
                    } else {
                        error(4212, Reuse_W);
                    }
                }
                if ((c = skip_cwhite()) != L')') {
                    UNGETCH();
                    error(4211);
                }
            } else {
                UNGETCH();
                error(4210);
            }

            swprintf(Reuse_W, L"#pragma code_page %d\r\n", cp);
            myfwrite(Reuse_W, wcslen(Reuse_W) * sizeof(WCHAR), 1, OUTPUTFILE);
        }
    }
    // Skip #pragma statements
    while((c = get_non_eof()) != L'\n');
    UNGETCH();
    return cp;
}
